package sg.edu.nus.iss.billing.datastore.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import sg.edu.nus.iss.billing.datastore.ISelector;
import sg.edu.nus.iss.billing.exception.DataStoreException;

/**
 * <h2>For selecting matchable data from pass in list,</h2> </br><b>the object
 * type in the list can't be primitive type, </br>and the attributes in the
 * object must apply standard POJO naming convention</b> </br>eg: the T can not
 * be String, Double, Date... </br> and if the attribute, used for comparing,
 * must implement the interface java.lang.Compariable. If the value in object is
 * null and the passed in value is null, will treat it match condition, if the
 * passed in value is not null will treat it not match condition <b>How to
 * use:</b></br> <code>
 * 	class User {</br>
 * 		private String name;</br>
 * 		private int age;</br>
 * 		public User(String name, int age){</br>
 * 		}
 * 		//get method apply to the POJO naming convention</br>
 * 		public String getName(){</br>
 * 			return this.name;</br>
 * 		}	</br>
 * 		//same for age</br>
 * 		...
 * 	}
 * </code> Suppose have a list:</br> <code>
 * 		List<User> list = new ArrayList<User></br>
 * 		User user1 = new User("A",10);</br>
 * 		User user2 = new User("A",12);</br>
 * 		User user3 = new User("B",9);</br>
 * 		...</br>
 * 		User usern = new User("C",n);</br>
 * 		list.add(user1)</br>
 * 		...</br>
 * 		list.add(usern)</br>
 * </code>
 * 
 * //Now I want to get all the user, which name is A and age is between
 * 10-20</br> <code>
 * 		Selector selector = new Selector();</br>
 * 		<b>// type of attribute name is String which is comparable</b></br>
 * 		<b>// type of attribute age is int(automatic encapsulate to Integer) which is comparable</b></br>
 * 		List<User> newList = selector.addEquals("name","A")</br>
 * 									.addGreatEqual("age",10)</br>
 * 									.addLessEqual("age",20)</br>
 * 									.filter(list);</br>
 * </br>
 * 		//the return list newList will contains only name is A and age is between 10-20</br>
 * </code>
 * 
 * @author Wang DaYong
 * 
 */
public class Selector implements ISelector {

	private List<Evaluator> evaluators = new ArrayList<Evaluator>();

	private List<Selector> logicOrs = null;

	/**
	 * pass in the equal condition
	 * 
	 * @param field
	 *            the field name
	 * @param value
	 *            excepted value
	 * @return
	 */
	public Selector eq(String field, Object value) {

		evaluators.add(new Evaluator(EvalType.eq, field, value));
		return this;
	}

	/**
	 * pass in the greater than condition
	 * 
	 * @param field
	 *            the field name
	 * @param value
	 *            excepted value
	 * @return
	 */
	public Selector gt(String field, Object value) {

		evaluators.add(new Evaluator(EvalType.gt, field, value));
		return this;
	}

	/**
	 * pass in the less than condition
	 * 
	 * @param field
	 *            the field name
	 * @param value
	 *            excepted value
	 * @return
	 */
	public Selector lt(String field, Object value) {

		evaluators.add(new Evaluator(EvalType.lt, field, value));
		return this;
	}

	/**
	 * pass in the great equal condition
	 * 
	 * @param field
	 *            the field name
	 * @param value
	 *            excepted value
	 * @return
	 */
	public Selector ge(String field, Object value) {

		evaluators.add(new Evaluator(EvalType.ge, field, value));
		return this;
	}

	/**
	 * pass in the less equal condition
	 * 
	 * @param field
	 *            the field name
	 * @param value
	 *            excepted value
	 * @return
	 */
	public Selector le(String field, Object value) {

		evaluators.add(new Evaluator(EvalType.le, field, value));
		return this;
	}

	public Selector ne(String field, Object value) {

		evaluators.add(new Evaluator(EvalType.ne, field, value));
		return this;
	}

	public Selector in(String field, List<Object> values) {

		evaluators.add(new Evaluator(EvalType.in, field, values));
		return this;
	}

	public Selector notin(String field, List<Object> values) {

		evaluators.add(new Evaluator(EvalType.notin, field, values));
		return this;
	}

	public Selector or(Selector selector) {

		if (logicOrs==null) {
			logicOrs = new ArrayList<Selector>();
		}
		logicOrs.add(selector);
		return this;
	}

	/**
	 * Using the selector to filter out unmatchable Object collection
	 * 
	 * @param <T>
	 * @param list
	 *            the data source to be filtered
	 * @return return the matchable list object
	 * @throws Exception
	 * @throws DataStoreException
	 */
	public <T extends Object> List<T> filter(Collection<T> list) throws Exception {

		if (list!=null) {
			List<T> newList = new ArrayList<T>();
			for (T obj : list) {
				boolean isSatisified = true;

				// check the first level evaluators
				for (Evaluator eval : evaluators) {
					if (!eval.isTrue(obj)) {
						isSatisified = false;
						break;
					}
				}

				// if meet the condition, add obj to result list
				if (isSatisified) {
					newList.add(obj);
				} else {
					// if still have logic or
					if (logicOrs!=null) {
						// do evaluation using inner selector
						for (Selector orSelector : logicOrs) {
							if (orSelector.isSatisfiied(obj)) {
								newList.add(obj);
							}
						}
					}
				}

			}
			return newList;
		}
		return null;
	}

	/**
	 * Test the object satisfied the evaluators
	 * 
	 * @param obj
	 * @return
	 * @throws Exception
	 */
	private boolean isSatisfiied(Object obj) throws Exception {

		boolean isSatisified = true;
		for (Evaluator eval : this.evaluators) {
			if (!eval.isTrue(obj)) {
				isSatisified = false;
				break;
			}
		}
		return isSatisified;
	}

	private enum EvalType {
		gt, lt, eq, le, ge, ne, in, notin
	}

	private class Evaluator {

		String filed = null;

		Object value = null;

		EvalType type = null;

		Evaluator(EvalType type, String field, Object value) {

			this.type = type;
			this.filed = field;
			this.value = value;
		}

		@SuppressWarnings("unchecked")
		boolean isTrue(Object origObj) throws Exception {

			boolean result = false;

			Comparable origValue = (Comparable) getValue(origObj, filed);

			if (origValue==null&&value!=null) {
				return false;
			}

			if (origValue==null&&value==null) {
				return true;
			}

			switch (type) {
				case eq:
					result = origValue.equals(value);
					break;
				case ne:
					result = !origValue.equals(value);
					break;
				case lt:
					result = (origValue.compareTo(value)<0) ? true : false;
					break;
				case le:
					result = (origValue.compareTo(value)<=0) ? true : false;
					break;
				case ge:
					result = (origValue.compareTo(value)>=0) ? true : false;
					break;
				case gt:
					result = (origValue.compareTo(value)>0) ? true : false;
					break;
				case in:
					List values = (List) value;
					result = values.contains(origValue);
					break;
				case notin:
					values = (List) value;
					result = !values.contains(origValue);
					break;
				default:
					result = false;
			}

			return result;
		}

		/**
		 * using reflection to get the value of the field
		 * 
		 * @param obj
		 * @param field
		 * @return
		 * @throws DataStoreException
		 */
		private Object getValue(Object obj, String field) throws Exception {

			String methodName = "get"+field.substring(0, 1).toUpperCase()+field.substring(1, field.length());
			try {
				Object o = null;
				Method method = obj.getClass().getMethod(methodName);
				if (method==null) {
					return null;
				} else {
					return method.invoke(obj);
				}
			} catch (SecurityException e) {
				throw e;
			} catch (NoSuchMethodException e) {
				throw e;
			} catch (IllegalArgumentException e) {
				throw e;
			} catch (IllegalAccessException e) {
				throw e;
			} catch (InvocationTargetException e) {
				throw e;
			}
		}

	}
}
